/*
 *  Copyright (C) 2006 Takeharu KATO
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "common.h"
#include <stddef.h>
#include <netdb.h>
#include <pwd.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>

int
internal_realloc(void **ptr,size_t new_size,size_t old_size){
  void *orig_ptr;
  ssize_t clr_len;

  if (!ptr)
    return -1;

  orig_ptr=*ptr;
  *ptr=realloc(*ptr, new_size);
  
  if (!(*ptr)) {
    *ptr=orig_ptr;
    return -2;
  }
  clr_len=new_size - old_size;
  if (clr_len > 0)
    memset( ((*ptr)+old_size),0,clr_len);
  return 0;
}
const char *
udp_get_peeraddr(const udp_con_t *con){
  int rc;
  struct addrinfo *info;

  g_assert(con);

  info=con->server_info;
  g_assert(info);

  rc=getnameinfo(info->ai_addr, info->ai_addrlen,
		 (char *)con->peer, NI_MAXHOST,
		NULL, 0, 
		NI_NUMERICHOST);

  g_assert (!rc);

  dbg_out("get peer host:%s (family,type,proto)=(%d,%d,%d)\n",
	  con->peer,
	  info->ai_family,
	  info->ai_socktype,
	  info->ai_protocol);

  return (const char *)con->peer;
}
int 
udp_setup_server(udp_con_t *con,int port,int family){
  int rc;
  int soc;
  char reuse=1;
  struct addrinfo *info;

  if (!con)
    return -EINVAL;

  memset(con,0,sizeof(udp_con_t));

  rc=setup_addr_info(&info, NULL,port,SOCK_DGRAM,family);
  if (rc<0)
    return rc;

  rc=socket(info->ai_family,info->ai_socktype,info->ai_protocol);
  if (rc<0) 
    goto err_out;

  soc=rc;

#ifdef IPV6_V6ONLY
  if (info->ai_family == AF_INET6) {
    int v6only=1;

    rc=setsockopt(soc,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&v6only,sizeof(v6only));
    if (rc) {
      goto err_out;
    }
  }
#endif /*  IPV6_V6ONLY  */

  rc=bind(soc,info->ai_addr,info->ai_addrlen);
  if (rc<0)
    goto err_out;

  con->client_info=info;
  con->soc=soc;
  con->family=family;
  con->port=port;
  /*
   *allocate server side
   */
  rc=setup_addr_info(&info, NULL,port,SOCK_DGRAM,family);
  if (rc<0)
    goto free_client_out;

  con->server_info=info;
  
  return 0;
 free_client_out:
  if (con->client_info)
    freeaddrinfo(con->client_info);
  con->client_info=NULL;
 err_out:
  err_out("Error:%s (%d)\n",strerror(errno),errno);
  if (info)
    freeaddrinfo(info);
  exit(rc);
  return rc;
}

int
udp_setup_client(udp_con_t *con,const char *hostname, int port,int family){
  int soc;
  int rc;
  struct addrinfo *info=NULL;

  if (!hostname)
    return -EINVAL;

  rc=setup_addr_info(&info, hostname,port,SOCK_DGRAM,family);
  if (rc<0)
    goto err_out;

  rc=socket(info->ai_family,info->ai_socktype,info->ai_protocol);
  if (rc<0) 
    goto free_out;
  
  soc=rc;

#ifdef IPV6_V6ONLY
  if (info->ai_family == AF_INET6) {
    int v6only=1;

    rc=setsockopt(soc,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&v6only,sizeof(v6only));
    if (rc) {
      goto err_out;
    }
  }
#endif /*  IPV6_V6ONLY  */

  con->server_info=info;
  con->soc=soc;
  con->family=family;
  con->port=port;

  return 0;

 free_out:
  if (info)
    freeaddrinfo(info);
 err_out:
  err_out("Error: %s (%d)\n",strerror(errno),errno);
  return rc;
}
int
udp_release_connection(udp_con_t *con) {
  if (!con)
    return -EINVAL;

  dbg_out("Close udp sock:%d\n",con->soc);
  if (con->client_info)
    freeaddrinfo(con->client_info);
  if (con->server_info)
    freeaddrinfo(con->server_info);

  con->client_info=NULL;
  con->server_info=NULL;

  close(con->soc);

  return 0;
}
int
udp_send_message(const udp_con_t *con,const char *ipaddr,int port, const char *msg,size_t len){
  int rc;
  struct addrinfo *info=NULL;

  if ((!con) || (!ipaddr) || (!msg) )
    return -EINVAL;

  dbg_out("send [addr:%s port:%d]:\n %s\n",ipaddr,port,msg);

  rc=setup_addr_info(&info,ipaddr,port,SOCK_DGRAM,con->family);
  if (rc<0)
    goto err_out;

  rc=sendto(con->soc,msg,len,0,info->ai_addr,info->ai_addrlen);

 err_out:
  if (info)
    freeaddrinfo(info);
  return rc;
}
int
udp_send_peer(const udp_con_t *con,const char *msg,size_t len){
  int rc;
  struct addrinfo *info;

  if ((!con) || (!msg) )
    return -EINVAL;

  dbg_out("send peer:%s\n",msg);
  info=con->server_info;
  g_assert(info);
  rc=sendto(con->soc,msg,len,0,info->ai_addr,info->ai_addrlen);

  return rc;
}

int
udp_send_broadcast_with_addr(const udp_con_t *con,const char *bcast,const char *msg,size_t len){
  int rc;
  struct addrinfo *info=NULL;

  if ((!con) || (!msg) )
    return -EINVAL;

  udp_enable_broadcast(con);

  dbg_out("%s\n",msg);

  if (!bcast) {
    switch(con->family) {
    case PF_INET6:
      rc=setup_addr_info(&info,"ff02::1",con->port,SOCK_DGRAM,con->family);
      break;
    default:
      rc=setup_addr_info(&info,"255.255.255.255",con->port,SOCK_DGRAM,con->family);
      break;
    }
  } else {
    rc=setup_addr_info(&info,bcast,con->port,SOCK_DGRAM,con->family);
  }
  if (rc<0)
    goto err_out;

  rc=sendto(con->soc,msg,len,0,info->ai_addr,info->ai_addrlen);

 err_out:
  if (info)
    freeaddrinfo(info);
  return rc;
}

int
udp_send_broadcast(const udp_con_t *con,const char *msg,size_t len){
  return udp_send_broadcast_with_addr(con,NULL,msg,len);
}
static int
udp_set_buffer(const udp_con_t *con){
  int rc;
  int size=0;

  if (!con)
    return -EINVAL;

  return sock_set_buffer(con->soc);;
}

int
udp_enable_broadcast(const udp_con_t *con){
  int rc;
  int flag;
  int confirm,confirm_len;

  if (!con) 
    return -EINVAL;

  dbg_out("Here\n");
  flag=1;
  rc=setsockopt(con->soc, SOL_SOCKET, SO_BROADCAST, (void*)&flag, sizeof(int));
  if (rc<0) {
    err_out("Can not set broad cast:%s(%d)\n",strerror(errno),errno);
    return -errno;
  }


  confirm=0;
  confirm_len=sizeof(confirm);
  rc=getsockopt(con->soc, SOL_SOCKET, SO_BROADCAST, (void*)&confirm, &confirm_len);
  if (rc<0) {
    err_out("Can not get broad cast:%s(%d)\n",strerror(errno),errno);
    return -errno;
  }
  if (confirm)
    dbg_out("Broadcat is set\n");
  else
    err_out("Can not set broad cast:%s(%d)\n",strerror(errno),errno);

  return 0;
}
int
udp_disable_broadcast(const udp_con_t *con){
  int rc;
  int flag;

  if (!con) 
    return -EINVAL;

  dbg_out("Here\n");
  flag=0;
  rc=setsockopt(con->soc, SOL_SOCKET, SO_BROADCAST, (void*)&flag, sizeof(int));
  if (rc<0) {
    err_out("Can not unset broad cast:%s(%d)\n",strerror(errno),errno);
    return -errno;
  }

  return 0;
}
int
udp_recv_message(const udp_con_t *con,char **msg,size_t *len){
  ssize_t recv_len;
  char *recv_buf=NULL;
  int old_fd_flags;
  int rc;
  int ret_code=0;
  struct addrinfo *info;

  if ( (!con) || (!msg) || (!len) )
    return -EINVAL;

  g_assert(*msg==NULL);  

  recv_buf=g_malloc(_MSG_BUF_SIZE);
  if (!recv_buf)
    return -ENOMEM;

  rc=udp_set_buffer(con);
  if (rc<0)
    goto err_out;

  rc=udp_enable_broadcast(con);
  if (rc<0)
    goto err_out;

  info=con->server_info;
  /*
   *  空読みする
   */

  recv_len=recvfrom(con->soc,recv_buf,_MSG_BUF_SIZE,(MSG_PEEK|MSG_DONTWAIT),info->ai_addr,&(info->ai_addrlen));
  if (recv_len<=0) {
    err_out("%s(errno:%d)\n",strerror(errno),errno);
    ret_code=-errno;
    goto err_out;
  }

  dbg_out("read:%d %s\n",recv_len,recv_buf);
  /*
   *  バッファに読み込む
   */
  recv_len=recvfrom(con->soc,recv_buf,recv_len,MSG_DONTWAIT,info->ai_addr,&(info->ai_addrlen));
  if (recv_len<0) {
    err_out("%s(errno:%d)\n",strerror(errno),errno);
    ret_code=-errno;
    goto err_out;
  }

  /*
   * バッファ獲得
   */
  
  g_assert(*msg==NULL);
  *msg=g_malloc(recv_len);
  if (!(*msg))
    goto err_out;
  dbg_out("copy:%d %s\n",recv_len,recv_buf);
  *len=recv_len;
  memmove(*msg,recv_buf,recv_len);  /*  内容をコピー  */

err_out:
  if (recv_buf)
    g_free(recv_buf);

  return ret_code;
}

